package apriori;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import mining.CsvReader;
import mining.ItemSetMining;
import mining.Itemset;

public class Apriori extends ItemSetMining {

	public Apriori(String filePath) {
		super(filePath);
	}

	public Map<Itemset, Integer> rare() {
		if (this.getMinFreqItemsetSupport() == 0 || this.getMinRareItemsetSupport() == 0) {
			throw new IllegalStateException("minimumFrequentItemsetSupport and minimumRareItemsetSupport must be set to non-zero.");
		} else {
			return removeHigherThanOrEqSupport(mine(getMinRareItemsetSupport()), getMinFreqItemsetSupport());
		}
	}

	public Map<Itemset, Integer> frequent() {
		if (this.getMinFreqItemsetSupport() == 0) {
			throw new IllegalStateException(
					"minimumFrequentItemsetSupport must be set to non-zero.");
		} else {
			return mine(this.getMinFreqItemsetSupport());
		}
	}

	private Map<Itemset, Integer> mine(int minimumSupport) {
		Vector<Integer> itemFreqs = this.findDbInfo();
		System.out.println("before: " + itemFreqs);
		itemFreqs = Itemset.removeBelowSup(itemFreqs, minimumSupport);
		System.out.println("after: " + itemFreqs);
//		int[] itemFreqs = this.countItemSupport2();
		
		Map<Itemset, Integer> itemsetFreqs = new LinkedHashMap<Itemset, Integer>();
//		Map<Itemset, Integer> itemsetFreqs = new HashMap<Itemset, Integer>();
		for (int i = 0; i < itemFreqs.size(); i++) {
			itemsetFreqs.put(new Itemset(i), itemFreqs.get(i));
		}
		
//		this.setItemCount(itemFreqs.size());
		
		Map<Itemset, Integer> tempFreqs = itemsetFreqs;
		for (int i = 2; ; i++) {
			System.out.print(".");
			boolean finished = true;
			
			// generate new candidates
			Set<Itemset> nextTier = genCandidates(tempFreqs.keySet());

			tempFreqs = new LinkedHashMap<Itemset, Integer>();
			for (CsvReader reader = new CsvReader(this.filePath); reader.hasNext(); ) {
				List<Integer> list = new ArrayList<Integer>(reader.read(Itemset.actualToConverted));
//				list.retainAll(items);
				if (list.isEmpty())
					continue;
				Itemset transaction = new Itemset(list);
				for (Itemset nextLevelSet : nextTier) {
					if (transaction.hasSubset(nextLevelSet)) {
						finished = false;
						if (!tempFreqs.containsKey(nextLevelSet)) {
							tempFreqs.put(nextLevelSet, 0);
//							System.out.println(nextLevelSet);
						}
						tempFreqs.put(nextLevelSet, tempFreqs.get(nextLevelSet) + 1);
					}
				}
			}
			for (Itemset key : tempFreqs.keySet()) {
				if (tempFreqs.get(key) >= minimumSupport) {
					itemsetFreqs.put(key, tempFreqs.get(key));
				}
			}
			if (finished) {
				break;
			}
		}
		System.out.println();
		return itemsetFreqs;
	}

	private Set<Itemset> genCandidates(
			Set<Itemset> currentFrequentGenerators) {
		Set<Itemset> nextTier = new LinkedHashSet<Itemset>();
//		Set<Itemset> nextTier = new HashSet<Itemset>();
		for (Itemset itemSet : currentFrequentGenerators) {
			for (int item = 0; item < this.getItemCount(); item++) {
				if (!itemSet.get(item)) {
					Itemset newKey = itemSet.copy();
					newKey.add(item);
					nextTier.add(newKey);
				}
			}
		}
		return nextTier;
	}

	private Set<Itemset> genCandidates_slow(Set<Itemset> currentFrequentGenerators) {
		Object[] curr = currentFrequentGenerators.toArray();
		// stores set of candidates of size i+1
		Set<Itemset> next = new HashSet<Itemset>(); 
		for (int i = 0; i < curr.length; i++) {
			for (int j = i + 1; j < curr.length; j++) {
				Itemset merged = mergeTwoLists1Diff(
						(Itemset) curr[i], (Itemset) curr[j]);
				if (merged != null) {
					next.add(merged);
				}
			}
		}
		return next;
	}

	private Itemset mergeTwoLists1Diff(Itemset a,
			Itemset b) {
		int diffCount = 0;
		Itemset result = b.copy();

		for (int i = 0; i < a.maxSize(); i++) {
			if (a.get(i) != b.get(i)) {
				if (diffCount++ > 2) {
					return null;
				}
				result.add(i);
			}
		}

		assert (a.size() == b.size());
		assert (result.size() == a.size() + 1);

		if (diffCount == 0) {
			throw new RuntimeException(
					"the two lists to be merged are the same");
		}
		return result;
	}

	private Map<Itemset, Integer> removeHigherThanOrEqSupport(
			Map<Itemset, Integer> itemsetFreqs, long maximumSupport) {
		Map<Itemset, Integer> results = new LinkedHashMap<Itemset, Integer>();
//		Map<Itemset, Integer> results = new HashMap<Itemset, Integer>();
		for (Itemset itemset : itemsetFreqs.keySet()) {
			if (itemsetFreqs.get(itemset) < maximumSupport) {
				results.put(itemset, itemsetFreqs.get(itemset));
			}
		}
		return results;
	}
}
